using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using Microsoft.SqlServer.Dts.Runtime;
using Microsoft.SqlServer.Dts.Pipeline.Wrapper;
using System.Xml.XPath;
using System.Collections;

namespace Microsoft.SharedSource.SqlServer.MDDE.Components
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1705:LongAcronymsShouldBePascalCased"),
    System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1708:IdentifiersShouldDifferByMoreThanCase")]
    public abstract class MDDEComponent
    {
        protected string name;
        protected string uri;
        protected Object innerComponent;
        protected List<MDDEComponent> successors;
        protected List<MDDEComponent> predecessors;
        protected XmlNode properties;
        protected Package package;

        protected enum ObjectType
        {
            TaskHost = 0,
            DataFlowTask = 1,
            Sequence = 2,
            ForLoop = 3,
            ForEachLoop = 4,
            DTSEventHandler = 5,
            IDTSMetadataComponent = 6,
            Package = 7,
            Variable = 8,
            Connection = 9
        }

        /// <summary>
        /// Creates an mdde component that is a wrapper over SSIS 
        /// component and provides additional features specific to MDDE.
        /// </summary>
        /// <param name="component"></param>
        /// <param name="name"></param>
        /// <param name="uri"></param>
        protected MDDEComponent(Package package, Object component,
            string name, string uri)
        {
            this.package = package;
            this.innerComponent = component;
            this.name = name;
            this.uri = uri;
            this.successors = new List<MDDEComponent>();
            this.Predecessors = new List<MDDEComponent>();
        }

        ///// <summary>
        ///// Remove this constructor.
        ///// </summary>
        ///// <param name="component"></param>
        ///// <param name="name"></param>
        ///// <param name="uri"></param>
        //protected MDDEComponent(Object component,
        //    string name, string uri)
        //{
        //    this.innerComponent = component;
        //    this.name = name;
        //    this.uri = uri;
        //    this.successors = new List<MDDEComponent>();
        //}

        /// <summary>
        /// The name of mdde component.
        /// </summary>
        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        /// <summary>
        /// A URI to locate the component
        /// </summary>
        public string Uri
        {
            get { return uri; }
            set { uri = value; }
        }

        /// <summary>
        /// Contained SSIS object.
        /// </summary>
        public object InnerComponent
        {
            get { return innerComponent; }
            set { innerComponent = value; }
        }

        /// <summary>
        /// List of mdde components that follow immediately 
        /// this component in the execution order.
        /// </summary>
        public List<MDDEComponent> Successors
        {
            get { return successors; }
            set { successors = value; }
        }

        /// <summary>
        /// List of mdde components that preceds immediately 
        /// this component in the execution order.
        /// </summary>
        public List<MDDEComponent> Predecessors
        {
            get { return predecessors; }
            set { predecessors = value; }
        }


        /// <summary>
        /// Component properties
        /// </summary>
        public XmlNode Properties
        {
            get { return properties; }
            set { properties = value; }
        }

        /// <summary>
        /// This method compares the component with another component.
        /// </summary>
        /// <param name="subject">The target mdde component.</param>
        /// <returns>True if equal, false otherwise.</returns>
        public virtual bool CompareTo(MDDEComponent subject, bool deepCompare)
        {
            if (this.GetType() == subject.GetType() && this.innerComponent.GetType() == subject.innerComponent.GetType() && this.name == subject.name)
                return true;
            return false;
        }

        /// <summary>
        /// Creates a changes xml node for the component.
        /// </summary>
        /// <param name="subject">The reference mdde component to compare against.</param>
        /// <returns>The changes xml node.</returns>
        public abstract void GetChangeXml(MDDEComponent subject, XmlDocument changeXml);

        /// <summary>
        /// This method applies changes specified to the inner SSIS 
        /// component and tries to fix the inner component.
        /// </summary>
        /// <param name="changeXml">Changes in the form of an xml.</param>
        /// <returns>True if the component is validated successfully after 
        /// the application of changes, false otherwise.</returns>
        public abstract bool ApplyChanges(XmlDocument changeXml, MDDEExecutionDAG originalPackage);

        /// <summary>
        /// Validates the inner SSIS component.
        /// </summary>
        /// <returns>True if the component is validated 
        /// successfully, false otherwise.</returns>
        public virtual bool Validate()
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// This method returns the difference with another component in terms of 
        /// addition/deletion of component. This function is only applicable to component, 
        /// who has the innerExecutionDAG.
        /// </summary>
        /// <param name="subject">The target mdde component.</param>
        /// <returns>Difference report in XML format.</returns>
        public virtual void GetDifference(MDDEComponent subject, XmlDocument differenceReport)
        {
            //Do nothing. 
        }

        /// <summary>
        /// This method returns the child component represented by the given uri.
        /// This function is only applicable to component, who has the innerExecutionDAG.
        /// </summary>
        /// <param name="uri">The uri of the component.</param>
        /// <returns>MDDEComponent if found, null otherwise</returns>
        public virtual MDDEComponent GetChildComponent(string uri)
        {
            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="referencePackage"></param>
        /// <param name="componentURI"></param>
        /// <param name="executableType"></param>
        /// <returns></returns>
        protected object GetComponent(Package referencePackage, string componentURI, ref ObjectType executableType)
        {
            char[] separator = new char[1];
            separator[0] = '/';

            string[] intermediateComponents = componentURI.Split(separator);

            string parentNodeType = "Package";
            object intermediateComponent = referencePackage;
            executableType = ObjectType.Package;

            for (int intermediateComponentLocation = 1; intermediateComponentLocation < intermediateComponents.Length;
                intermediateComponentLocation++)
            {
                separator[0] = '=';
                string[] intermediateComponentDetails =
                    intermediateComponents[intermediateComponentLocation].Split(separator);

                if (intermediateComponentDetails.Length != 2)
                    return null;

                switch (intermediateComponentDetails[0])
                {
                    case "Executable":

                        if (parentNodeType == "Package")
                        {
                            intermediateComponent = referencePackage.Executables[intermediateComponentDetails[1]];
                            parentNodeType = "Executable";
                            executableType = GetExecutableType(referencePackage.Executables[intermediateComponentDetails[1]]);
                        }

                        else if (parentNodeType == "Executable")
                        {
                            #region "Get Correct Executable Container"

                            switch (executableType)
                            {
                                case ObjectType.Sequence:
                                    Sequence sequence = ((Executable)intermediateComponent) as Sequence;
                                    intermediateComponent = sequence.Executables[intermediateComponentDetails[1]];
                                    break;

                                case ObjectType.ForLoop:
                                    ForLoop forloop = ((Executable)intermediateComponent) as ForLoop;
                                    intermediateComponent = forloop.Executables[intermediateComponentDetails[1]];
                                    break;

                                case ObjectType.ForEachLoop:
                                    ForEachLoop foreachloop = ((Executable)intermediateComponent) as ForEachLoop;
                                    intermediateComponent = foreachloop.Executables[intermediateComponentDetails[1]];
                                    break;
                            }

                            #endregion

                            executableType = GetExecutableType((Executable)intermediateComponent);
                            parentNodeType = "Executable";
                        }

                        else if (parentNodeType == "EventHandler")
                        {
                            DtsEventHandler eventHandler = (DtsEventHandler)intermediateComponent;
                            intermediateComponent = eventHandler.Executables[intermediateComponentDetails[1]];
                            executableType = GetExecutableType(eventHandler.Executables[intermediateComponentDetails[1]]);
                            parentNodeType = "Executable";
                        }

                        else
                            return null;

                        break;

                    case "EventHandler":

                        if (parentNodeType == "Package")
                        {
                            intermediateComponent = referencePackage.EventHandlers[intermediateComponentDetails[1]];
                            parentNodeType = "EventHandler";
                            executableType = ObjectType.DTSEventHandler;
                        }

                        else if (parentNodeType == "Executable")
                        {
                            #region "Get Correct Executable Container"

                            switch (executableType)
                            {
                                case ObjectType.TaskHost:
                                case ObjectType.DataFlowTask:

                                    TaskHost taskHost = ((Executable)intermediateComponent) as TaskHost;
                                    intermediateComponent = taskHost.EventHandlers[intermediateComponentDetails[1]];

                                    break;

                                case ObjectType.Sequence:

                                    Sequence sequence = ((Executable)intermediateComponent) as Sequence;
                                    intermediateComponent = sequence.EventHandlers[intermediateComponentDetails[1]];

                                    break;

                                case ObjectType.ForLoop:

                                    ForLoop forloop = ((Executable)intermediateComponent) as ForLoop;
                                    intermediateComponent = forloop.EventHandlers[intermediateComponentDetails[1]];

                                    break;

                                case ObjectType.ForEachLoop:

                                    ForEachLoop foreachloop = ((Executable)intermediateComponent) as ForEachLoop;
                                    intermediateComponent = foreachloop.EventHandlers[intermediateComponentDetails[1]];

                                    break;
                            }

                            #endregion

                            parentNodeType = "EventHandler";
                            executableType = ObjectType.DTSEventHandler;
                        }

                        else
                            return null;

                        break;

                    case "Component":

                        if (parentNodeType == "Executable")
                        {
                            TaskHost taskHost = ((Executable)intermediateComponent) as TaskHost;
                            MainPipe dataFlow = taskHost.InnerObject as MainPipe;

                            intermediateComponent = dataFlow.ComponentMetaDataCollection[intermediateComponentDetails[1]];
                            parentNodeType = "Component";
                            executableType = ObjectType.IDTSMetadataComponent;
                        }

                        else
                            return null;

                        break;
                }
            }

            return intermediateComponent;
        }

        /// <summary>
        /// Creates a tree structure for this 
        /// component that can be rendered in a UI.
        /// 
        /// The base class contains the implementation 
        /// that would create a package tree corresponding 
        /// to a complete SSIS package.
        /// 
        /// Override this method to create Nodes 
        /// other than of the type "Package".
        /// </summary>
        /// <returns></returns>
        public virtual XmlDocument GetComponentTree()
        {
            // TODO: Make this method abstract.
            return null;
        }

        #region Helper Methods

        private ObjectType GetExecutableType(Executable executable)
        {
            ObjectType execType = ObjectType.TaskHost;
            string type = string.Empty;
            type = executable.GetType().Name;

            switch (type)
            {
                case "Sequence":

                    execType = ObjectType.Sequence;
                    break;

                case "ForLoop":

                    execType = ObjectType.ForLoop;
                    break;

                case "ForEachLoop":

                    execType = ObjectType.ForEachLoop;
                    break;

                case "TaskHost":

                    TaskHost thExec = executable as TaskHost;
                    MainPipe dfPipe = thExec.InnerObject as MainPipe;

                    if (dfPipe != null)
                        execType = ObjectType.DataFlowTask;
                    else
                        execType = ObjectType.TaskHost;

                    break;
            }

            return execType;
        }

        #region Xml document management methods

        protected static XmlElement AppendChildElement(XmlNode parentElement,
            string childelementName, string[] attributeFollowedByValue)
        {
            return AppendChildElement(
                parentElement, childelementName,
                null, attributeFollowedByValue);
        }

        protected static XmlElement AppendChildElement(XmlNode parentElement,
            string childelementName, string innerXml,
            string[] attributeFollowedByValue)
        {
            XmlElement childElement;
            if (parentElement is XmlDocument)
                // Special treatment for XmlDocument.
                childElement =
                    ((XmlDocument)parentElement).
                    CreateElement(childelementName);
            else
                childElement =
                    parentElement.OwnerDocument.
                    CreateElement(childelementName);

            parentElement.AppendChild(childElement);

            if (!string.IsNullOrEmpty(innerXml))
                childElement.InnerXml = innerXml;

            AppendAttribute(childElement, attributeFollowedByValue);

            return childElement;
        }

        protected static void AppendAttribute(XmlElement parentElement,
            string[] attributeFollowedByValue)
        {
            if (attributeFollowedByValue == null)
                return;

            for (int i = 0; i < attributeFollowedByValue.Length; i++)
            {
                XmlAttribute attrib =
                    parentElement.OwnerDocument.
                    CreateAttribute(attributeFollowedByValue[i]);
                parentElement.Attributes.Append(attrib);

                i++;

                attrib.Value =
                    (i >= attributeFollowedByValue.Length) ?
                    string.Empty : attributeFollowedByValue[i];
            }
        }

        #endregion

        #endregion

        protected void AppendDtsProperties(
            XmlElement xRoot, IDTSPropertiesProvider iDTSPropertiesProvider)
        {
            foreach (DtsProperty prop in iDTSPropertiesProvider.Properties)
                if (prop.Get && prop.Set && !(prop is IEnumerable))
                    AppendChildElement(
                        xRoot,
                        "Property",
                        new string[] { 
                            "Name", prop.Name,
                            "Value", 
                            prop.GetValue(iDTSPropertiesProvider) == null ? 
                            string.Empty : 
                            prop.GetValue(iDTSPropertiesProvider).ToString()
                        });
        }
    }
}
